﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;

namespace intelliEssay.Core.DocumentStructure
{
    /// <summary>
    /// This class records the key elment sequence of a document.
    /// <para>
    /// </summary>
    public class DocumentKeyElementSequence
    {
        /// <summary>
        /// <para>This method is used to generate given task's DocumentKeyElementSequence.</para>
        /// <para>If the standardElementSequence of the task is not initialized, it is initialized based on the standardFormat.docx path</para>
        /// </summary>
        /// <param name="tsk"></param>
        /// <returns>a DocumentKeyElementSequence generated based on the task</returns>
        public static DocumentKeyElementSequence GenerateDocumentElementSequence(Task tsk)
        {
            try
            {
                if (tsk.taskSetting.standardDocumentKeyElementSequence.documentKeyElmentSequence == null)
                {
                    List<DocumentParts> parts = new List<DocumentParts>();
                    Dictionary<DocumentParts, Paragraph> dict = new Dictionary<DocumentParts, Paragraph>();
                    DocumentKeyElementSequence dke = new DocumentKeyElementSequence();
                    tsk.taskSetting.standardDocument = WordprocessingDocument.Open(tsk.taskSetting.StandardFormatDocumentPath, false);
                    IEnumerable<Paragraph> paras = tsk.taskSetting.standardDocument.MainDocumentPart.Document.Body.Elements<Paragraph>().Where<Paragraph>(o =>
                        {
                            var attris = o.GetAttributes();
                            foreach (OpenXmlAttribute oxa in attris)
                            {
                                if (oxa.LocalName == "keyPart" || oxa.LocalName=="conditional")
                                {
                                    return true;
                                }
                            }
                            return false;
                        });
                    Array.ForEach<Paragraph>(paras.ToArray<Paragraph>(), new Action<Paragraph>(o =>
                        {
                            foreach (DocumentParts dp in Enum.GetValues(typeof(DocumentParts)))
                            {
                                var attr = o.GetAttributes().Where<OpenXmlAttribute>(x => x.LocalName == "documentPart" && x.Value.ToLowerInvariant() == dp.ToString().ToLowerInvariant());
                                if (attr.Count() != 0 && !dict.ContainsKey(dp))
                                {
                                    parts.Add(dp);
                                    dict.Add(dp, o);
                                }
                            }
                        }));
                    dke.documentKeyElmentSequence = parts.ToArray<DocumentParts>();
                    dke.documentKeyElementParagraphDictionary = dict;
                    tsk.taskSetting.standardDocumentKeyElementSequence = dke;
                }
                var dsDict = new Dictionary<DocumentParts, Paragraph>();
                var result = tsk.taskSetting.standardDocumentKeyElementSequence.documentKeyElmentSequence.Where<DocumentParts>(o =>
                    {
                        try
                        {
                            var para = tsk.documentStructure.GetDocumentKeyElementParagraphByName(o);
                            if (para != null)
                            {
                                dsDict.Add(o, para);
                                return true;
                            }
                            return false;
                        }
                        catch
                        {
                            return false;
                        }
                    });
                result = result.OrderBy<DocumentParts, DocumentParts>(o => o, new DocumentPartsSequenceComparer(tsk.documentStructure));
                return new DocumentKeyElementSequence() { documentKeyElmentSequence = result.ToArray<DocumentParts>(), documentKeyElementParagraphDictionary = dsDict };
            }
            catch (Exception ex)
            {
                throw new ApplicationException("This is not an intelliEssay-Compliant standard format Microsoft(R) Office Document", ex);
            }
        }

        /// <summary>
        /// Compares two key elements of the text, regarding on the standardElementSequence.
        /// <para>If dp1 should appear BEFORE dp2, 1 is returned</para>
        /// <para>If dp2 should appear BEFORE dp1, -1 is returned</para>
        /// <para>If dp1 and dp2 are the same, or dp1 or dp2 is Undefined as a documentKey part, 0 is returned.</para>
        /// <param name="dp1">the first keydoc element</param>
        /// <param name="dp2">the second keydoc element</param>
        /// <param name="standardElementSequence">the standard element sequence that is used to do the comparison</param>
        /// <returns></returns> 
        /// /// </summary>
        public static int Compare(DocumentParts dp1, DocumentParts dp2, DocumentKeyElementSequence standardElementSequence)
        {
            int idx1 = -1;
            int idx2 = -1;
            for (int i = 0; i < standardElementSequence.documentKeyElmentSequence.Length; i++)
            {
                if (standardElementSequence.documentKeyElmentSequence[i] == dp1)
                {
                    idx1 = i;
                }
                if (standardElementSequence.documentKeyElmentSequence[i] == dp2)
                {
                    idx2 = i;
                }
                if (idx1 != -1 && idx2 != -1)
                {
                    break;
                }
            }
            if (idx1 == -1 || idx2 == -1) return 0;
            if (idx1 > idx2) return -1;
            if (idx2 > idx1) return 1;
            return 0;
        }
        /// <summary>
        /// The key elements' sequence
        /// <para>if an element appears BEFORE another, this means that element should also</para>
        /// <para>appear before it in all documents</para>
        /// </summary>
        public DocumentParts[] documentKeyElmentSequence;
        /// <summary>
        /// Corresponds to documentKeyElementSequence. 
        /// </summary>
        public Dictionary<DocumentParts, Paragraph> documentKeyElementParagraphDictionary;
    }
    /// <summary>
    /// This class is used when comapring two document key element
    /// </summary>
    internal class DocumentPartsSequenceComparer : IComparer<DocumentParts>
    {
        public DocumentStructure ds;
        public DocumentPartsSequenceComparer(DocumentStructure _ds)
        {
            ds = _ds;
        }
        public int Compare(DocumentParts dp1, DocumentParts dp2)
        {
            try
            {
                int[] doNothing = new int[2];
                if (dp2 == DocumentParts.MainDocumentBody)
                {
                }
                var idx1 = ds.GetDocumentKeyElementIndexByName(dp1, out doNothing[0]);
                var idx2 = ds.GetDocumentKeyElementIndexByName(dp2, out doNothing[1]);
                if (idx1 > idx2)
                {
                    return 1;
                }
                if (idx2 == idx1)
                {
                    return 0;
                }
                else
                {
                    return -1;
                }
            }
            catch
            {
                return 0;
            }
        }
    }
}
